import sys, re, os, string, sets
from SVG import KeyGen, ScriptGen
from Lib import Util, StateDBHelper
from math import log10
from subprocess import Popen, PIPE

#Colors:
#Orange: FF5A00
#Blue: 0033CC
#Yellow: FFD400

if len(sys.argv) < 2:
    print 'Usage: %s all|file_list' % sys.argv[0]
    sys.exit(1)
    
MAX_DISPLAY_WIDTH = 1008
DEST_AS_STR = '<g id="%s" class="node" onmouseover="mouseOverPrefix(evt, %s)" onmouseout="mouseOutPrefix(evt, %s)"><title>%s</title>\n'
NODE_STR = '<g id="%s" class="node" onclick="nodeClick(evt, %s)" onmouseover="mouseOverNode(evt, %s)" onmouseout="mouseOutNode(evt, %s)">\
<title>%s</title>\n'
EDGE_STR = '''<g id='%s' class="edge" onclick="edgeClick(evt, '%s')" onmouseover="mouseOverEdge(evt, '%s')" onmouseout="mouseOutEdge(evt, '%s')">\
<title>%s</title>\n'''
SVG_START_STR = '''<svg onload="init(evt)" viewBox="0.00 0.00 %s %s" xmlns="http://www.w3.org/2000/svg" \
xmlns:xlink="http://www.w3.org/1999/xlink">\n'''

SVG_START_PAT = re.compile('<svg.*width="(\d+\.?\d*)pt".*height="(\d+\.?\d*)pt".*')
SVG_VIEW_BOX_PAT = re.compile('(.*viewBox="\d+\.\d* \d+\.\d* )(\d+\.\d*) (\d+\.\d*)(".*>)$')
SVG_END_PAT = re.compile('</svg>$')
START_GRAPH_PAT = re.compile('(<g id="graph0".*scale\()(\d+\.?\d*)(.* translate\(\d+ )(\d+\.?\d*)(.*)')
TITLE_PAT = re.compile('<title>(.*)</title>')
NODE_EDGE_PAT = re.compile('<g.*class="(node|edge)".*<title>(.*)</title>')
STYLE_ITEM_PAT = re.compile('([^";:]*:[^;"]*)')
STYLE_PAT = re.compile('<(path|polygon|text)(.*)style="([^"]*)"(.*>)$')

NODES_PER_RANK = 12
FONT_SIZE = 20

destAs = sets.Set()
processingGraphObject = False
processingSvgStart = False
gId = None
Util.output_dir = '/somewhere/graphs'

def makeOutputDir():
    global prefix
    rootDir = '%s/%s' % (Util.output_dir, prefix.split('.')[0])
    if not os.path.isdir(rootDir):
        os.makedirs(rootDir)
    outputDir = '%s/%s' % (rootDir, string.replace(prefix, '/', '_'))
    if not os.path.isdir(outputDir):
        os.makedirs(outputDir)
    return outputDir

def unpackStyle(match, gId):
    styles = re.findall(STYLE_ITEM_PAT, match.group(3))
    type = match.group(1)
    
    objId = '%s%s' % (gId,type)
    ret = 'id="%s" ' % objId
    count = 0
    for style in styles:
        count += 1
        tokens = style.split(':', 2)
        if (tokens[0] != "font-family"):
            ret += tokens[0]
            ret += '="%s" ' % tokens[1]
        #print "Style %i: %s" % (count, style)
    return ret

def processLine(line, output):
    global processingGraphObject, processingSvgStart, gId, prefixId, prefix, width, height, scale
    
    if processingGraphObject:
        if (line[0:3] != '</g'):
            styleMo = re.match(STYLE_PAT, line)
            if styleMo:
                output.write('<%s%s %s%s\n' % (styleMo.group(1), styleMo.group(2), unpackStyle(styleMo, gId), styleMo.group(4)))
            else:
                output.write(line)
        else:
            output.write(line)
            processingGraphObject = False
        return

    if processingSvgStart:
        mo = re.match(START_GRAPH_PAT, line)
        if not mo:
            return
        
        scale = float(mo.group(2))
        yTranslate = float(mo.group(4))
        keyGen.setSize(width, height, scale)
        keyGen.createGraphKey()
        width = keyGen.graphWidth * scale
        height += keyGen.keyHeight * scale
        output.write(SVG_START_STR % (width, height))
        output.write(keyGen.keyStr)
        output.write('%s%s%s%s%s font-family="%s">' %\
                     (mo.group(1), mo.group(2), mo.group(3), yTranslate + keyGen.keyHeight, mo.group(5)[:-1], KeyGen.FONT_FAMILY_STR))
        processingSvgStart = False    
        return    
        
    mo = re.match(SVG_START_PAT, line)
    if mo:
        processingSvgStart = True 
        width = int(mo.group(1))
        height = int(mo.group(2))
        return
        
    mo = re.match(NODE_EDGE_PAT, line)
    if mo:
        type = mo.group(1)
        title = mo.group(2)
        if type == "node":
            gId = '%s' % title
            if (int(gId) in destAs):
                output.write(DEST_AS_STR % (gId, gId, gId, title))
            else:
                output.write(NODE_STR % (gId, gId, gId, gId, title))
        elif type == "edge":
            tokens = title.split("&#45;&gt;")
            gId = '%sto%s' % (tokens[0], tokens[1])
            output.write(EDGE_STR % (gId, gId, gId, gId, title))
        else:
            output.write(line)
            return
            
        processingGraphObject = True
        return
    
    if (re.search(SVG_END_PAT, line)):
        destAsMap = {}
        for dest in destAs:
                titles = dbHelper.query("SELECT primary_title, secondary_title FROM as_lookup WHERE as_num=%s" % dest)
                if titles:
                    destAsMap[dest] = titles[0]
                else:
                    destAsMap[dest] = ('Unknown', 'Unknown')
        prefixStats = dbHelper.query("SELECT updates, non_dup_ann, dup_ann, tot_with FROM updates WHERE prefix_id=%s" % prefixId)[0]
        keyGen.createMouseOverBox(dbHelper.getElapsedTime(), prefix, destAsMap, prefixStats) 
        output.write(keyGen.mouseOverStr)
        output.write(scriptGen.getScriptStr(prefixId, keyGen.graphWidth, keyGen.graphHeight + keyGen.keyHeight))
        output.write(line)
        return
        
    output.write(line)

def getStyleForEdge(child, parent, perAct):
    width = (.75 + perAct*2) * 2
    arrowsize = .7 + perAct/2
    hexVal = hex(int(255*(1.0 - (.5 + perAct * .5))))
    hexValStr = ('%s' % hexVal)[2:4]
    weight = int(perAct * 10) + 1
    if (len(hexValStr) == 1):
        hexValStr = '0%s' % hexValStr
    color = "#%s%s%s" % (hexValStr, hexValStr, hexValStr)
    return '\tedge [color=\"%s\" arrowsize=%s weight=%s style=\"setlinewidth(%s)\"];\n\t%s -> %s;\n'\
     % (color, arrowsize, weight, width, child, parent)

def getStyleForNode(node, nSwitch, perAct):
    height = .4 + perAct * .35
    width = height * 1.75
    fontsize = int(round(14 + perAct * 8))
    logNSwitch = min(log10(nSwitch+1),5)    
    scale = int(255*(1 - logNSwitch/5.0))
    #color range is from FFFFFF to 0033CC(blue)    
    hexVals = [('%s' % hex(scale))[2:4]]
    fontcolor = (logNSwitch > 3) and 'white' or 'black' 
    hexVals.append(('%s' % hex(int(round(scale * .8 + 51))))[2:4])
    hexVals.append(('%s' % hex(int(round(scale * .2 + 204))))[2:4])
    for i in range(0,3):
        if (len(hexVals[i]) < 2):
            hexVals[i] = '0%s' % hexVals[i]
            
    return '\t%s [shape=box, height=%s, width=%s, fillcolor="#%s%s%s", color="#0033CC", style=\"setlinewidth(3),filled\", \
    fontsize=%s, fontcolor=%s];\n' % (node, height, width, hexVals[0], hexVals[1], hexVals[2], fontsize, fontcolor)
       
def getStyleForPeer(node, nSwitch, perAct):
    height = .4 + perAct * .35
    logNSwitch = min(log10(nSwitch+1),5)
    width = height * 1.75
    fontsize = int(round(14 + perAct * 8))
    scale = int(255*(1 - logNSwitch/5.0))
    fontcolor = (logNSwitch > 3) and 'white' or 'black'
    #color range is from FFFFFF to FF5A00 (orange)
    hexVals = [('%s' % hex(int(round(scale * .647 + 90))))[2:4]]
    hexVals.append(('%s' % hex(scale))[2:4])
    for i in range(0,2):
        if (len(hexVals[i]) < 2):
            hexVals[i] = '0%s' % hexVals[i]
    return '\t%s [shape=box, height=%s, width=%s, fillcolor="#ff%s%s", color="#FFA500", style=\"setlinewidth(3),filled\", \
    fontsize=%s, fontcolor=%s];\n' % (node, height, width, hexVals[0], hexVals[1], fontsize, fontcolor)
    
# Construct a full dot script to produce the time graph
def dotScript ():
    global destAs, prefixId, prefix
    destAs.clear()
    peers = sets.Set()
    script = 'digraph G {\n'
    script += '\trankdir=LR;\n'
    script += '\tsize="%s,50";\n' % (MAX_DISPLAY_WIDTH / 72)
    script += '\tratio=compress;\n'
    script += '\tnode[fontname=Helvetica];\n' 
    
    pathStates = dbHelper.query("SELECT as_num, switch_count, per_act, path FROM peer_states WHERE prefix_id=%s" % prefixId)
    for i in range(0,len(pathStates)):
        state = pathStates[i]
        script += getStyleForPeer(state[0], state[1], state[2])
        peers.add(state[0])
        destAs.add(int(state[3].rsplit(',',1)[-1]))

    script += '\t{rank = sink; '
    for node in destAs:
        script += '%s; ' % node
    script += '}\n'
    
    nodeStates = dbHelper.query("SELECT node_states.as_num, node_states.switch_count, node_states.per_act, node_ranks.rank \
    FROM node_states LEFT JOIN node_ranks ON node_states.as_num = node_ranks.as_num \
    WHERE node_ranks.prefix_id=%s AND node_states.prefix_id=%s ORDER BY node_ranks.rank" % (prefixId, prefixId))
    for i in range (0, len(nodeStates)):
        state = nodeStates[i]
        if i % NODES_PER_RANK == 0:
            rankStr = '\t{rank = same; '            
        rankStr += '%s; ' % state[0]
        if i % NODES_PER_RANK == NODES_PER_RANK - 1:
            rankStr += '}\n'
            script += rankStr
        if state[0] in destAs:
            script += '\t%s [label="%s" shape=polygon,sides=5,height=1.5,width=1.5,fillcolor="#FFD400",\
            style="setlinewidth(3.5),filled",fontsize=24];\n' % (state[0], prefix)
        elif state[0] in peers:
            None
        else:
            script += getStyleForNode(state[0], state[1], state[2])
    
    edgeStates = dbHelper.query("SELECT as_num,as_parent,per_act FROM parent_states WHERE prefix_id=%s" % prefixId)
    for state in edgeStates:
        script += getStyleForEdge(state[0], state[1], state[2])

    script += '}'
    return script   
    
def processPrefix():
    global prefix, prefixId
    print "Processing prefix %s id %s" % (prefix, prefixId)
    
    dotFile = dotScript()
    temp = open('./last.dot', 'w')
    temp.write(dotFile)
    
    pEcho = Popen(["echo", "-e", dotFile], stdout=PIPE)
    pDot = Popen(["dot", "-Tsvg"], stdout=PIPE, stdin=pEcho.stdout)
    svgText = pDot.communicate()[0]
    processingGraphObject = False
    
    keyGen.prefix = prefix
    dates = dbHelper.query("SELECT start_date, end_date FROM prefixes WHERE id=%s" % prefixId)
    keyGen.startDate = dates[0][0]
    keyGen.endDate = dates[0][1]
    
    output = open('%s/map.svg' % makeOutputDir(), 'w')

    for line in svgText.split('\n'):
        processLine(line, output)
    
    dbHelper.addGraphSize(width, height)
    output.close()
    
keyGen = KeyGen.KeyGen(MAX_DISPLAY_WIDTH)
dbHelper = StateDBHelper.StateDBHelper()
scriptGen = ScriptGen.ScriptGen(dbHelper, MAX_DISPLAY_WIDTH)

if sys.argv[1] == 'all':
    print "Loading ip list..."
    for row in dbHelper.getIdIpMaskList():
        dbHelper.id = row[0]
        prefixId = int(row[0])
        prefix = row[1] + '/' + str(row[2])
        processPrefix()
else:
    for p in sys.argv[1:]:
        prefix = p
        (dotDecimalIp, mask) = prefix.split('/')
        dbHelper.setPrefix(Util.decimalIp(dotDecimalIp), int(mask))
        prefixId = dbHelper.id
        processPrefix()

print "********************DONE**************************"